<h1 id="俄罗斯方块游戏">俄罗斯方块游戏</h1>
<p>本章我们要制作一个俄罗斯方块游戏。</p>
<h2 id="tetris">Tetris</h2>
<blockquote>
<p>译注：称呼：方块是由四个小方格组成的</p>
</blockquote>
<p>俄罗斯方块游戏是世界上最流行的游戏之一。是由一名叫Alexey Pajitnov的俄罗斯程序员在1985年制作的，从那时起，这个游戏就风靡了各个游戏平台。</p>
<p>俄罗斯方块归类为下落块迷宫游戏。游戏有7个基本形状：S、Z、T、L、反向L、直线、方块，每个形状都由4个方块组成，方块最终都会落到屏幕底部。所以玩家通过控制形状的左右位置和旋转，让每个形状都以合适的位置落下，如果有一行全部被方块填充，这行就会消失，并且得分。游戏结束的条件是有形状接触到了屏幕顶部。</p>
<p>方块展示：</p>
<figure>
<img class="whitelist" src="docs/PyQt5/images/11-tetrominoes.png" alt="tetrominoes" />
</figure>
<p>PyQt5是专门为创建图形界面产生的，里面一些专门为制作游戏而开发的组件，所以PyQt5是能制作小游戏的。</p>
<p>制作电脑游戏也是提高自己编程能力的一种很好的方式。</p>
<h2 id="开发">开发</h2>
<p>没有图片，所以就自己用绘画画出来几个图形。每个游戏里都有数学模型的，这个也是。</p>
<p>开工之前：</p>
<ul>
<li>用<code>QtCore.QBasicTimer()</code>创建一个游戏循环</li>
<li>模型是一直下落的</li>
<li>模型的运动是以小块为基础单位的，不是按像素</li>
<li>从数学意义上来说，模型就是就是一串数字而已</li>
</ul>
<p>代码由四个类组成：Tetris, Board, Tetrominoe和Shape。Tetris类创建游戏，Board是游戏主要逻辑。Tetrominoe包含了所有的砖块，Shape是所有砖块的代码。</p>
<div class="sourceCode" id="cb1"><pre><code class="language-python"><a class="sourceLine" id="cb1-1" data-line-number="1"><span class="co">#!/usr/bin/python3</span></a>
<a class="sourceLine" id="cb1-2" data-line-number="2"><span class="co"># -*- coding: utf-8 -*-</span></a>
<a class="sourceLine" id="cb1-3" data-line-number="3"></a>
<a class="sourceLine" id="cb1-4" data-line-number="4"><span class="co">&quot;&quot;&quot;</span></a>
<a class="sourceLine" id="cb1-5" data-line-number="5"><span class="co">ZetCode PyQt5 tutorial </span></a>
<a class="sourceLine" id="cb1-6" data-line-number="6"></a>
<a class="sourceLine" id="cb1-7" data-line-number="7"><span class="co">This is a Tetris game clone.</span></a>
<a class="sourceLine" id="cb1-8" data-line-number="8"></a>
<a class="sourceLine" id="cb1-9" data-line-number="9"><span class="co">Author: Jan Bodnar</span></a>
<a class="sourceLine" id="cb1-10" data-line-number="10"><span class="co">Website: zetcode.com </span></a>
<a class="sourceLine" id="cb1-11" data-line-number="11"><span class="co">Last edited: August 2017</span></a>
<a class="sourceLine" id="cb1-12" data-line-number="12"><span class="co">&quot;&quot;&quot;</span></a>
<a class="sourceLine" id="cb1-13" data-line-number="13"></a>
<a class="sourceLine" id="cb1-14" data-line-number="14"><span class="im">from</span> PyQt5.QtWidgets <span class="im">import</span> QMainWindow, QFrame, QDesktopWidget, QApplication</a>
<a class="sourceLine" id="cb1-15" data-line-number="15"><span class="im">from</span> PyQt5.QtCore <span class="im">import</span> Qt, QBasicTimer, pyqtSignal</a>
<a class="sourceLine" id="cb1-16" data-line-number="16"><span class="im">from</span> PyQt5.QtGui <span class="im">import</span> QPainter, QColor </a>
<a class="sourceLine" id="cb1-17" data-line-number="17"><span class="im">import</span> sys, random</a>
<a class="sourceLine" id="cb1-18" data-line-number="18"></a>
<a class="sourceLine" id="cb1-19" data-line-number="19"><span class="kw">class</span> Tetris(QMainWindow):</a>
<a class="sourceLine" id="cb1-20" data-line-number="20">    </a>
<a class="sourceLine" id="cb1-21" data-line-number="21">    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-22" data-line-number="22">        <span class="bu">super</span>().<span class="fu">__init__</span>()</a>
<a class="sourceLine" id="cb1-23" data-line-number="23">        </a>
<a class="sourceLine" id="cb1-24" data-line-number="24">        <span class="va">self</span>.initUI()</a>
<a class="sourceLine" id="cb1-25" data-line-number="25">        </a>
<a class="sourceLine" id="cb1-26" data-line-number="26">        </a>
<a class="sourceLine" id="cb1-27" data-line-number="27">    <span class="kw">def</span> initUI(<span class="va">self</span>):    </a>
<a class="sourceLine" id="cb1-28" data-line-number="28">        <span class="co">&#39;&#39;&#39;initiates application UI&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-29" data-line-number="29"></a>
<a class="sourceLine" id="cb1-30" data-line-number="30">        <span class="va">self</span>.tboard <span class="op">=</span> Board(<span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-31" data-line-number="31">        <span class="va">self</span>.setCentralWidget(<span class="va">self</span>.tboard)</a>
<a class="sourceLine" id="cb1-32" data-line-number="32"></a>
<a class="sourceLine" id="cb1-33" data-line-number="33">        <span class="va">self</span>.statusbar <span class="op">=</span> <span class="va">self</span>.statusBar()        </a>
<a class="sourceLine" id="cb1-34" data-line-number="34">        <span class="va">self</span>.tboard.msg2Statusbar[<span class="bu">str</span>].<span class="ex">connect</span>(<span class="va">self</span>.statusbar.showMessage)</a>
<a class="sourceLine" id="cb1-35" data-line-number="35">        </a>
<a class="sourceLine" id="cb1-36" data-line-number="36">        <span class="va">self</span>.tboard.start()</a>
<a class="sourceLine" id="cb1-37" data-line-number="37">        </a>
<a class="sourceLine" id="cb1-38" data-line-number="38">        <span class="va">self</span>.resize(<span class="dv">180</span>, <span class="dv">380</span>)</a>
<a class="sourceLine" id="cb1-39" data-line-number="39">        <span class="va">self</span>.center()</a>
<a class="sourceLine" id="cb1-40" data-line-number="40">        <span class="va">self</span>.setWindowTitle(<span class="st">&#39;Tetris&#39;</span>)        </a>
<a class="sourceLine" id="cb1-41" data-line-number="41">        <span class="va">self</span>.show()</a>
<a class="sourceLine" id="cb1-42" data-line-number="42">        </a>
<a class="sourceLine" id="cb1-43" data-line-number="43"></a>
<a class="sourceLine" id="cb1-44" data-line-number="44">    <span class="kw">def</span> center(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-45" data-line-number="45">        <span class="co">&#39;&#39;&#39;centers the window on the screen&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-46" data-line-number="46">        </a>
<a class="sourceLine" id="cb1-47" data-line-number="47">        screen <span class="op">=</span> QDesktopWidget().screenGeometry()</a>
<a class="sourceLine" id="cb1-48" data-line-number="48">        size <span class="op">=</span> <span class="va">self</span>.geometry()</a>
<a class="sourceLine" id="cb1-49" data-line-number="49">        <span class="va">self</span>.move((screen.width()<span class="op">-</span>size.width())<span class="op">/</span><span class="dv">2</span>, </a>
<a class="sourceLine" id="cb1-50" data-line-number="50">            (screen.height()<span class="op">-</span>size.height())<span class="op">/</span><span class="dv">2</span>)</a>
<a class="sourceLine" id="cb1-51" data-line-number="51">        </a>
<a class="sourceLine" id="cb1-52" data-line-number="52"></a>
<a class="sourceLine" id="cb1-53" data-line-number="53"><span class="kw">class</span> Board(QFrame):</a>
<a class="sourceLine" id="cb1-54" data-line-number="54">    </a>
<a class="sourceLine" id="cb1-55" data-line-number="55">    msg2Statusbar <span class="op">=</span> pyqtSignal(<span class="bu">str</span>)</a>
<a class="sourceLine" id="cb1-56" data-line-number="56">    </a>
<a class="sourceLine" id="cb1-57" data-line-number="57">    BoardWidth <span class="op">=</span> <span class="dv">10</span></a>
<a class="sourceLine" id="cb1-58" data-line-number="58">    BoardHeight <span class="op">=</span> <span class="dv">22</span></a>
<a class="sourceLine" id="cb1-59" data-line-number="59">    Speed <span class="op">=</span> <span class="dv">300</span></a>
<a class="sourceLine" id="cb1-60" data-line-number="60"></a>
<a class="sourceLine" id="cb1-61" data-line-number="61">    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, parent):</a>
<a class="sourceLine" id="cb1-62" data-line-number="62">        <span class="bu">super</span>().<span class="fu">__init__</span>(parent)</a>
<a class="sourceLine" id="cb1-63" data-line-number="63">        </a>
<a class="sourceLine" id="cb1-64" data-line-number="64">        <span class="va">self</span>.initBoard()</a>
<a class="sourceLine" id="cb1-65" data-line-number="65">        </a>
<a class="sourceLine" id="cb1-66" data-line-number="66">        </a>
<a class="sourceLine" id="cb1-67" data-line-number="67">    <span class="kw">def</span> initBoard(<span class="va">self</span>):     </a>
<a class="sourceLine" id="cb1-68" data-line-number="68">        <span class="co">&#39;&#39;&#39;initiates board&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-69" data-line-number="69"></a>
<a class="sourceLine" id="cb1-70" data-line-number="70">        <span class="va">self</span>.timer <span class="op">=</span> QBasicTimer()</a>
<a class="sourceLine" id="cb1-71" data-line-number="71">        <span class="va">self</span>.isWaitingAfterLine <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-72" data-line-number="72">        </a>
<a class="sourceLine" id="cb1-73" data-line-number="73">        <span class="va">self</span>.curX <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-74" data-line-number="74">        <span class="va">self</span>.curY <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-75" data-line-number="75">        <span class="va">self</span>.numLinesRemoved <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-76" data-line-number="76">        <span class="va">self</span>.board <span class="op">=</span> []</a>
<a class="sourceLine" id="cb1-77" data-line-number="77"></a>
<a class="sourceLine" id="cb1-78" data-line-number="78">        <span class="va">self</span>.setFocusPolicy(Qt.StrongFocus)</a>
<a class="sourceLine" id="cb1-79" data-line-number="79">        <span class="va">self</span>.isStarted <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-80" data-line-number="80">        <span class="va">self</span>.isPaused <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-81" data-line-number="81">        <span class="va">self</span>.clearBoard()</a>
<a class="sourceLine" id="cb1-82" data-line-number="82">        </a>
<a class="sourceLine" id="cb1-83" data-line-number="83">        </a>
<a class="sourceLine" id="cb1-84" data-line-number="84">    <span class="kw">def</span> shapeAt(<span class="va">self</span>, x, y):</a>
<a class="sourceLine" id="cb1-85" data-line-number="85">        <span class="co">&#39;&#39;&#39;determines shape at the board position&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-86" data-line-number="86">        </a>
<a class="sourceLine" id="cb1-87" data-line-number="87">        <span class="cf">return</span> <span class="va">self</span>.board[(y <span class="op">*</span> Board.BoardWidth) <span class="op">+</span> x]</a>
<a class="sourceLine" id="cb1-88" data-line-number="88"></a>
<a class="sourceLine" id="cb1-89" data-line-number="89">        </a>
<a class="sourceLine" id="cb1-90" data-line-number="90">    <span class="kw">def</span> setShapeAt(<span class="va">self</span>, x, y, shape):</a>
<a class="sourceLine" id="cb1-91" data-line-number="91">        <span class="co">&#39;&#39;&#39;sets a shape at the board&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-92" data-line-number="92">        </a>
<a class="sourceLine" id="cb1-93" data-line-number="93">        <span class="va">self</span>.board[(y <span class="op">*</span> Board.BoardWidth) <span class="op">+</span> x] <span class="op">=</span> shape</a>
<a class="sourceLine" id="cb1-94" data-line-number="94">        </a>
<a class="sourceLine" id="cb1-95" data-line-number="95"></a>
<a class="sourceLine" id="cb1-96" data-line-number="96">    <span class="kw">def</span> squareWidth(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-97" data-line-number="97">        <span class="co">&#39;&#39;&#39;returns the width of one square&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-98" data-line-number="98">        </a>
<a class="sourceLine" id="cb1-99" data-line-number="99">        <span class="cf">return</span> <span class="va">self</span>.contentsRect().width() <span class="op">//</span> Board.BoardWidth</a>
<a class="sourceLine" id="cb1-100" data-line-number="100">        </a>
<a class="sourceLine" id="cb1-101" data-line-number="101"></a>
<a class="sourceLine" id="cb1-102" data-line-number="102">    <span class="kw">def</span> squareHeight(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-103" data-line-number="103">        <span class="co">&#39;&#39;&#39;returns the height of one square&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-104" data-line-number="104">        </a>
<a class="sourceLine" id="cb1-105" data-line-number="105">        <span class="cf">return</span> <span class="va">self</span>.contentsRect().height() <span class="op">//</span> Board.BoardHeight</a>
<a class="sourceLine" id="cb1-106" data-line-number="106">        </a>
<a class="sourceLine" id="cb1-107" data-line-number="107"></a>
<a class="sourceLine" id="cb1-108" data-line-number="108">    <span class="kw">def</span> start(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-109" data-line-number="109">        <span class="co">&#39;&#39;&#39;starts game&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-110" data-line-number="110">        </a>
<a class="sourceLine" id="cb1-111" data-line-number="111">        <span class="cf">if</span> <span class="va">self</span>.isPaused:</a>
<a class="sourceLine" id="cb1-112" data-line-number="112">            <span class="cf">return</span></a>
<a class="sourceLine" id="cb1-113" data-line-number="113"></a>
<a class="sourceLine" id="cb1-114" data-line-number="114">        <span class="va">self</span>.isStarted <span class="op">=</span> <span class="va">True</span></a>
<a class="sourceLine" id="cb1-115" data-line-number="115">        <span class="va">self</span>.isWaitingAfterLine <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-116" data-line-number="116">        <span class="va">self</span>.numLinesRemoved <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-117" data-line-number="117">        <span class="va">self</span>.clearBoard()</a>
<a class="sourceLine" id="cb1-118" data-line-number="118"></a>
<a class="sourceLine" id="cb1-119" data-line-number="119">        <span class="va">self</span>.msg2Statusbar.emit(<span class="bu">str</span>(<span class="va">self</span>.numLinesRemoved))</a>
<a class="sourceLine" id="cb1-120" data-line-number="120"></a>
<a class="sourceLine" id="cb1-121" data-line-number="121">        <span class="va">self</span>.newPiece()</a>
<a class="sourceLine" id="cb1-122" data-line-number="122">        <span class="va">self</span>.timer.start(Board.Speed, <span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-123" data-line-number="123"></a>
<a class="sourceLine" id="cb1-124" data-line-number="124">        </a>
<a class="sourceLine" id="cb1-125" data-line-number="125">    <span class="kw">def</span> pause(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-126" data-line-number="126">        <span class="co">&#39;&#39;&#39;pauses game&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-127" data-line-number="127">        </a>
<a class="sourceLine" id="cb1-128" data-line-number="128">        <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.isStarted:</a>
<a class="sourceLine" id="cb1-129" data-line-number="129">            <span class="cf">return</span></a>
<a class="sourceLine" id="cb1-130" data-line-number="130"></a>
<a class="sourceLine" id="cb1-131" data-line-number="131">        <span class="va">self</span>.isPaused <span class="op">=</span> <span class="kw">not</span> <span class="va">self</span>.isPaused</a>
<a class="sourceLine" id="cb1-132" data-line-number="132">        </a>
<a class="sourceLine" id="cb1-133" data-line-number="133">        <span class="cf">if</span> <span class="va">self</span>.isPaused:</a>
<a class="sourceLine" id="cb1-134" data-line-number="134">            <span class="va">self</span>.timer.stop()</a>
<a class="sourceLine" id="cb1-135" data-line-number="135">            <span class="va">self</span>.msg2Statusbar.emit(<span class="st">&quot;paused&quot;</span>)</a>
<a class="sourceLine" id="cb1-136" data-line-number="136">            </a>
<a class="sourceLine" id="cb1-137" data-line-number="137">        <span class="cf">else</span>:</a>
<a class="sourceLine" id="cb1-138" data-line-number="138">            <span class="va">self</span>.timer.start(Board.Speed, <span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-139" data-line-number="139">            <span class="va">self</span>.msg2Statusbar.emit(<span class="bu">str</span>(<span class="va">self</span>.numLinesRemoved))</a>
<a class="sourceLine" id="cb1-140" data-line-number="140"></a>
<a class="sourceLine" id="cb1-141" data-line-number="141">        <span class="va">self</span>.update()</a>
<a class="sourceLine" id="cb1-142" data-line-number="142"></a>
<a class="sourceLine" id="cb1-143" data-line-number="143">        </a>
<a class="sourceLine" id="cb1-144" data-line-number="144">    <span class="kw">def</span> paintEvent(<span class="va">self</span>, event):</a>
<a class="sourceLine" id="cb1-145" data-line-number="145">        <span class="co">&#39;&#39;&#39;paints all shapes of the game&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-146" data-line-number="146">        </a>
<a class="sourceLine" id="cb1-147" data-line-number="147">        painter <span class="op">=</span> QPainter(<span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-148" data-line-number="148">        rect <span class="op">=</span> <span class="va">self</span>.contentsRect()</a>
<a class="sourceLine" id="cb1-149" data-line-number="149"></a>
<a class="sourceLine" id="cb1-150" data-line-number="150">        boardTop <span class="op">=</span> rect.bottom() <span class="op">-</span> Board.BoardHeight <span class="op">*</span> <span class="va">self</span>.squareHeight()</a>
<a class="sourceLine" id="cb1-151" data-line-number="151"></a>
<a class="sourceLine" id="cb1-152" data-line-number="152">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(Board.BoardHeight):</a>
<a class="sourceLine" id="cb1-153" data-line-number="153">            <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(Board.BoardWidth):</a>
<a class="sourceLine" id="cb1-154" data-line-number="154">                shape <span class="op">=</span> <span class="va">self</span>.shapeAt(j, Board.BoardHeight <span class="op">-</span> i <span class="op">-</span> <span class="dv">1</span>)</a>
<a class="sourceLine" id="cb1-155" data-line-number="155">                </a>
<a class="sourceLine" id="cb1-156" data-line-number="156">                <span class="cf">if</span> shape <span class="op">!=</span> Tetrominoe.NoShape:</a>
<a class="sourceLine" id="cb1-157" data-line-number="157">                    <span class="va">self</span>.drawSquare(painter,</a>
<a class="sourceLine" id="cb1-158" data-line-number="158">                        rect.left() <span class="op">+</span> j <span class="op">*</span> <span class="va">self</span>.squareWidth(),</a>
<a class="sourceLine" id="cb1-159" data-line-number="159">                        boardTop <span class="op">+</span> i <span class="op">*</span> <span class="va">self</span>.squareHeight(), shape)</a>
<a class="sourceLine" id="cb1-160" data-line-number="160"></a>
<a class="sourceLine" id="cb1-161" data-line-number="161">        <span class="cf">if</span> <span class="va">self</span>.curPiece.shape() <span class="op">!=</span> Tetrominoe.NoShape:</a>
<a class="sourceLine" id="cb1-162" data-line-number="162">            </a>
<a class="sourceLine" id="cb1-163" data-line-number="163">            <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-164" data-line-number="164">                </a>
<a class="sourceLine" id="cb1-165" data-line-number="165">                x <span class="op">=</span> <span class="va">self</span>.curX <span class="op">+</span> <span class="va">self</span>.curPiece.x(i)</a>
<a class="sourceLine" id="cb1-166" data-line-number="166">                y <span class="op">=</span> <span class="va">self</span>.curY <span class="op">-</span> <span class="va">self</span>.curPiece.y(i)</a>
<a class="sourceLine" id="cb1-167" data-line-number="167">                <span class="va">self</span>.drawSquare(painter, rect.left() <span class="op">+</span> x <span class="op">*</span> <span class="va">self</span>.squareWidth(),</a>
<a class="sourceLine" id="cb1-168" data-line-number="168">                    boardTop <span class="op">+</span> (Board.BoardHeight <span class="op">-</span> y <span class="op">-</span> <span class="dv">1</span>) <span class="op">*</span> <span class="va">self</span>.squareHeight(),</a>
<a class="sourceLine" id="cb1-169" data-line-number="169">                    <span class="va">self</span>.curPiece.shape())</a>
<a class="sourceLine" id="cb1-170" data-line-number="170"></a>
<a class="sourceLine" id="cb1-171" data-line-number="171">                    </a>
<a class="sourceLine" id="cb1-172" data-line-number="172">    <span class="kw">def</span> keyPressEvent(<span class="va">self</span>, event):</a>
<a class="sourceLine" id="cb1-173" data-line-number="173">        <span class="co">&#39;&#39;&#39;processes key press events&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-174" data-line-number="174">        </a>
<a class="sourceLine" id="cb1-175" data-line-number="175">        <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.isStarted <span class="kw">or</span> <span class="va">self</span>.curPiece.shape() <span class="op">==</span> Tetrominoe.NoShape:</a>
<a class="sourceLine" id="cb1-176" data-line-number="176">            <span class="bu">super</span>(Board, <span class="va">self</span>).keyPressEvent(event)</a>
<a class="sourceLine" id="cb1-177" data-line-number="177">            <span class="cf">return</span></a>
<a class="sourceLine" id="cb1-178" data-line-number="178"></a>
<a class="sourceLine" id="cb1-179" data-line-number="179">        key <span class="op">=</span> event.key()</a>
<a class="sourceLine" id="cb1-180" data-line-number="180">        </a>
<a class="sourceLine" id="cb1-181" data-line-number="181">        <span class="cf">if</span> key <span class="op">==</span> Qt.Key_P:</a>
<a class="sourceLine" id="cb1-182" data-line-number="182">            <span class="va">self</span>.pause()</a>
<a class="sourceLine" id="cb1-183" data-line-number="183">            <span class="cf">return</span></a>
<a class="sourceLine" id="cb1-184" data-line-number="184">            </a>
<a class="sourceLine" id="cb1-185" data-line-number="185">        <span class="cf">if</span> <span class="va">self</span>.isPaused:</a>
<a class="sourceLine" id="cb1-186" data-line-number="186">            <span class="cf">return</span></a>
<a class="sourceLine" id="cb1-187" data-line-number="187">                </a>
<a class="sourceLine" id="cb1-188" data-line-number="188">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_Left:</a>
<a class="sourceLine" id="cb1-189" data-line-number="189">            <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece, <span class="va">self</span>.curX <span class="op">-</span> <span class="dv">1</span>, <span class="va">self</span>.curY)</a>
<a class="sourceLine" id="cb1-190" data-line-number="190">            </a>
<a class="sourceLine" id="cb1-191" data-line-number="191">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_Right:</a>
<a class="sourceLine" id="cb1-192" data-line-number="192">            <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece, <span class="va">self</span>.curX <span class="op">+</span> <span class="dv">1</span>, <span class="va">self</span>.curY)</a>
<a class="sourceLine" id="cb1-193" data-line-number="193">            </a>
<a class="sourceLine" id="cb1-194" data-line-number="194">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_Down:</a>
<a class="sourceLine" id="cb1-195" data-line-number="195">            <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece.rotateRight(), <span class="va">self</span>.curX, <span class="va">self</span>.curY)</a>
<a class="sourceLine" id="cb1-196" data-line-number="196">            </a>
<a class="sourceLine" id="cb1-197" data-line-number="197">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_Up:</a>
<a class="sourceLine" id="cb1-198" data-line-number="198">            <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece.rotateLeft(), <span class="va">self</span>.curX, <span class="va">self</span>.curY)</a>
<a class="sourceLine" id="cb1-199" data-line-number="199">            </a>
<a class="sourceLine" id="cb1-200" data-line-number="200">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_Space:</a>
<a class="sourceLine" id="cb1-201" data-line-number="201">            <span class="va">self</span>.dropDown()</a>
<a class="sourceLine" id="cb1-202" data-line-number="202">            </a>
<a class="sourceLine" id="cb1-203" data-line-number="203">        <span class="cf">elif</span> key <span class="op">==</span> Qt.Key_D:</a>
<a class="sourceLine" id="cb1-204" data-line-number="204">            <span class="va">self</span>.oneLineDown()</a>
<a class="sourceLine" id="cb1-205" data-line-number="205">            </a>
<a class="sourceLine" id="cb1-206" data-line-number="206">        <span class="cf">else</span>:</a>
<a class="sourceLine" id="cb1-207" data-line-number="207">            <span class="bu">super</span>(Board, <span class="va">self</span>).keyPressEvent(event)</a>
<a class="sourceLine" id="cb1-208" data-line-number="208">                </a>
<a class="sourceLine" id="cb1-209" data-line-number="209"></a>
<a class="sourceLine" id="cb1-210" data-line-number="210">    <span class="kw">def</span> timerEvent(<span class="va">self</span>, event):</a>
<a class="sourceLine" id="cb1-211" data-line-number="211">        <span class="co">&#39;&#39;&#39;handles timer event&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-212" data-line-number="212">        </a>
<a class="sourceLine" id="cb1-213" data-line-number="213">        <span class="cf">if</span> event.timerId() <span class="op">==</span> <span class="va">self</span>.timer.timerId():</a>
<a class="sourceLine" id="cb1-214" data-line-number="214">            </a>
<a class="sourceLine" id="cb1-215" data-line-number="215">            <span class="cf">if</span> <span class="va">self</span>.isWaitingAfterLine:</a>
<a class="sourceLine" id="cb1-216" data-line-number="216">                <span class="va">self</span>.isWaitingAfterLine <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-217" data-line-number="217">                <span class="va">self</span>.newPiece()</a>
<a class="sourceLine" id="cb1-218" data-line-number="218">            <span class="cf">else</span>:</a>
<a class="sourceLine" id="cb1-219" data-line-number="219">                <span class="va">self</span>.oneLineDown()</a>
<a class="sourceLine" id="cb1-220" data-line-number="220">                </a>
<a class="sourceLine" id="cb1-221" data-line-number="221">        <span class="cf">else</span>:</a>
<a class="sourceLine" id="cb1-222" data-line-number="222">            <span class="bu">super</span>(Board, <span class="va">self</span>).timerEvent(event)</a>
<a class="sourceLine" id="cb1-223" data-line-number="223"></a>
<a class="sourceLine" id="cb1-224" data-line-number="224">            </a>
<a class="sourceLine" id="cb1-225" data-line-number="225">    <span class="kw">def</span> clearBoard(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-226" data-line-number="226">        <span class="co">&#39;&#39;&#39;clears shapes from the board&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-227" data-line-number="227">        </a>
<a class="sourceLine" id="cb1-228" data-line-number="228">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(Board.BoardHeight <span class="op">*</span> Board.BoardWidth):</a>
<a class="sourceLine" id="cb1-229" data-line-number="229">            <span class="va">self</span>.board.append(Tetrominoe.NoShape)</a>
<a class="sourceLine" id="cb1-230" data-line-number="230"></a>
<a class="sourceLine" id="cb1-231" data-line-number="231">        </a>
<a class="sourceLine" id="cb1-232" data-line-number="232">    <span class="kw">def</span> dropDown(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-233" data-line-number="233">        <span class="co">&#39;&#39;&#39;drops down a shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-234" data-line-number="234">        </a>
<a class="sourceLine" id="cb1-235" data-line-number="235">        newY <span class="op">=</span> <span class="va">self</span>.curY</a>
<a class="sourceLine" id="cb1-236" data-line-number="236">        </a>
<a class="sourceLine" id="cb1-237" data-line-number="237">        <span class="cf">while</span> newY <span class="op">&gt;</span> <span class="dv">0</span>:</a>
<a class="sourceLine" id="cb1-238" data-line-number="238">            </a>
<a class="sourceLine" id="cb1-239" data-line-number="239">            <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece, <span class="va">self</span>.curX, newY <span class="op">-</span> <span class="dv">1</span>):</a>
<a class="sourceLine" id="cb1-240" data-line-number="240">                <span class="cf">break</span></a>
<a class="sourceLine" id="cb1-241" data-line-number="241">                </a>
<a class="sourceLine" id="cb1-242" data-line-number="242">            newY <span class="op">-=</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb1-243" data-line-number="243"></a>
<a class="sourceLine" id="cb1-244" data-line-number="244">        <span class="va">self</span>.pieceDropped()</a>
<a class="sourceLine" id="cb1-245" data-line-number="245">        </a>
<a class="sourceLine" id="cb1-246" data-line-number="246"></a>
<a class="sourceLine" id="cb1-247" data-line-number="247">    <span class="kw">def</span> oneLineDown(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-248" data-line-number="248">        <span class="co">&#39;&#39;&#39;goes one line down with a shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-249" data-line-number="249">        </a>
<a class="sourceLine" id="cb1-250" data-line-number="250">        <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece, <span class="va">self</span>.curX, <span class="va">self</span>.curY <span class="op">-</span> <span class="dv">1</span>):</a>
<a class="sourceLine" id="cb1-251" data-line-number="251">            <span class="va">self</span>.pieceDropped()</a>
<a class="sourceLine" id="cb1-252" data-line-number="252">            </a>
<a class="sourceLine" id="cb1-253" data-line-number="253"></a>
<a class="sourceLine" id="cb1-254" data-line-number="254">    <span class="kw">def</span> pieceDropped(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-255" data-line-number="255">        <span class="co">&#39;&#39;&#39;after dropping shape, remove full lines and create new shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-256" data-line-number="256">        </a>
<a class="sourceLine" id="cb1-257" data-line-number="257">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-258" data-line-number="258">            </a>
<a class="sourceLine" id="cb1-259" data-line-number="259">            x <span class="op">=</span> <span class="va">self</span>.curX <span class="op">+</span> <span class="va">self</span>.curPiece.x(i)</a>
<a class="sourceLine" id="cb1-260" data-line-number="260">            y <span class="op">=</span> <span class="va">self</span>.curY <span class="op">-</span> <span class="va">self</span>.curPiece.y(i)</a>
<a class="sourceLine" id="cb1-261" data-line-number="261">            <span class="va">self</span>.setShapeAt(x, y, <span class="va">self</span>.curPiece.shape())</a>
<a class="sourceLine" id="cb1-262" data-line-number="262"></a>
<a class="sourceLine" id="cb1-263" data-line-number="263">        <span class="va">self</span>.removeFullLines()</a>
<a class="sourceLine" id="cb1-264" data-line-number="264"></a>
<a class="sourceLine" id="cb1-265" data-line-number="265">        <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.isWaitingAfterLine:</a>
<a class="sourceLine" id="cb1-266" data-line-number="266">            <span class="va">self</span>.newPiece()</a>
<a class="sourceLine" id="cb1-267" data-line-number="267">            </a>
<a class="sourceLine" id="cb1-268" data-line-number="268"></a>
<a class="sourceLine" id="cb1-269" data-line-number="269">    <span class="kw">def</span> removeFullLines(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-270" data-line-number="270">        <span class="co">&#39;&#39;&#39;removes all full lines from the board&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-271" data-line-number="271">        </a>
<a class="sourceLine" id="cb1-272" data-line-number="272">        numFullLines <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-273" data-line-number="273">        rowsToRemove <span class="op">=</span> []</a>
<a class="sourceLine" id="cb1-274" data-line-number="274"></a>
<a class="sourceLine" id="cb1-275" data-line-number="275">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(Board.BoardHeight):</a>
<a class="sourceLine" id="cb1-276" data-line-number="276">            </a>
<a class="sourceLine" id="cb1-277" data-line-number="277">            n <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-278" data-line-number="278">            <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(Board.BoardWidth):</a>
<a class="sourceLine" id="cb1-279" data-line-number="279">                <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.shapeAt(j, i) <span class="op">==</span> Tetrominoe.NoShape:</a>
<a class="sourceLine" id="cb1-280" data-line-number="280">                    n <span class="op">=</span> n <span class="op">+</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb1-281" data-line-number="281"></a>
<a class="sourceLine" id="cb1-282" data-line-number="282">            <span class="cf">if</span> n <span class="op">==</span> <span class="dv">10</span>:</a>
<a class="sourceLine" id="cb1-283" data-line-number="283">                rowsToRemove.append(i)</a>
<a class="sourceLine" id="cb1-284" data-line-number="284"></a>
<a class="sourceLine" id="cb1-285" data-line-number="285">        rowsToRemove.reverse()</a>
<a class="sourceLine" id="cb1-286" data-line-number="286">        </a>
<a class="sourceLine" id="cb1-287" data-line-number="287"></a>
<a class="sourceLine" id="cb1-288" data-line-number="288">        <span class="cf">for</span> m <span class="kw">in</span> rowsToRemove:</a>
<a class="sourceLine" id="cb1-289" data-line-number="289">            </a>
<a class="sourceLine" id="cb1-290" data-line-number="290">            <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(m, Board.BoardHeight):</a>
<a class="sourceLine" id="cb1-291" data-line-number="291">                <span class="cf">for</span> l <span class="kw">in</span> <span class="bu">range</span>(Board.BoardWidth):</a>
<a class="sourceLine" id="cb1-292" data-line-number="292">                        <span class="va">self</span>.setShapeAt(l, k, <span class="va">self</span>.shapeAt(l, k <span class="op">+</span> <span class="dv">1</span>))</a>
<a class="sourceLine" id="cb1-293" data-line-number="293"></a>
<a class="sourceLine" id="cb1-294" data-line-number="294">        numFullLines <span class="op">=</span> numFullLines <span class="op">+</span> <span class="bu">len</span>(rowsToRemove)</a>
<a class="sourceLine" id="cb1-295" data-line-number="295"></a>
<a class="sourceLine" id="cb1-296" data-line-number="296">        <span class="cf">if</span> numFullLines <span class="op">&gt;</span> <span class="dv">0</span>:</a>
<a class="sourceLine" id="cb1-297" data-line-number="297">            </a>
<a class="sourceLine" id="cb1-298" data-line-number="298">            <span class="va">self</span>.numLinesRemoved <span class="op">=</span> <span class="va">self</span>.numLinesRemoved <span class="op">+</span> numFullLines</a>
<a class="sourceLine" id="cb1-299" data-line-number="299">            <span class="va">self</span>.msg2Statusbar.emit(<span class="bu">str</span>(<span class="va">self</span>.numLinesRemoved))</a>
<a class="sourceLine" id="cb1-300" data-line-number="300">                </a>
<a class="sourceLine" id="cb1-301" data-line-number="301">            <span class="va">self</span>.isWaitingAfterLine <span class="op">=</span> <span class="va">True</span></a>
<a class="sourceLine" id="cb1-302" data-line-number="302">            <span class="va">self</span>.curPiece.setShape(Tetrominoe.NoShape)</a>
<a class="sourceLine" id="cb1-303" data-line-number="303">            <span class="va">self</span>.update()</a>
<a class="sourceLine" id="cb1-304" data-line-number="304">            </a>
<a class="sourceLine" id="cb1-305" data-line-number="305"></a>
<a class="sourceLine" id="cb1-306" data-line-number="306">    <span class="kw">def</span> newPiece(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-307" data-line-number="307">        <span class="co">&#39;&#39;&#39;creates a new shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-308" data-line-number="308">        </a>
<a class="sourceLine" id="cb1-309" data-line-number="309">        <span class="va">self</span>.curPiece <span class="op">=</span> Shape()</a>
<a class="sourceLine" id="cb1-310" data-line-number="310">        <span class="va">self</span>.curPiece.setRandomShape()</a>
<a class="sourceLine" id="cb1-311" data-line-number="311">        <span class="va">self</span>.curX <span class="op">=</span> Board.BoardWidth <span class="op">//</span> <span class="dv">2</span> <span class="op">+</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb1-312" data-line-number="312">        <span class="va">self</span>.curY <span class="op">=</span> Board.BoardHeight <span class="op">-</span> <span class="dv">1</span> <span class="op">+</span> <span class="va">self</span>.curPiece.minY()</a>
<a class="sourceLine" id="cb1-313" data-line-number="313">        </a>
<a class="sourceLine" id="cb1-314" data-line-number="314">        <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.tryMove(<span class="va">self</span>.curPiece, <span class="va">self</span>.curX, <span class="va">self</span>.curY):</a>
<a class="sourceLine" id="cb1-315" data-line-number="315">            </a>
<a class="sourceLine" id="cb1-316" data-line-number="316">            <span class="va">self</span>.curPiece.setShape(Tetrominoe.NoShape)</a>
<a class="sourceLine" id="cb1-317" data-line-number="317">            <span class="va">self</span>.timer.stop()</a>
<a class="sourceLine" id="cb1-318" data-line-number="318">            <span class="va">self</span>.isStarted <span class="op">=</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-319" data-line-number="319">            <span class="va">self</span>.msg2Statusbar.emit(<span class="st">&quot;Game over&quot;</span>)</a>
<a class="sourceLine" id="cb1-320" data-line-number="320"></a>
<a class="sourceLine" id="cb1-321" data-line-number="321"></a>
<a class="sourceLine" id="cb1-322" data-line-number="322"></a>
<a class="sourceLine" id="cb1-323" data-line-number="323">    <span class="kw">def</span> tryMove(<span class="va">self</span>, newPiece, newX, newY):</a>
<a class="sourceLine" id="cb1-324" data-line-number="324">        <span class="co">&#39;&#39;&#39;tries to move a shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-325" data-line-number="325">        </a>
<a class="sourceLine" id="cb1-326" data-line-number="326">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-327" data-line-number="327">            </a>
<a class="sourceLine" id="cb1-328" data-line-number="328">            x <span class="op">=</span> newX <span class="op">+</span> newPiece.x(i)</a>
<a class="sourceLine" id="cb1-329" data-line-number="329">            y <span class="op">=</span> newY <span class="op">-</span> newPiece.y(i)</a>
<a class="sourceLine" id="cb1-330" data-line-number="330">            </a>
<a class="sourceLine" id="cb1-331" data-line-number="331">            <span class="cf">if</span> x <span class="op">&lt;</span> <span class="dv">0</span> <span class="kw">or</span> x <span class="op">&gt;=</span> Board.BoardWidth <span class="kw">or</span> y <span class="op">&lt;</span> <span class="dv">0</span> <span class="kw">or</span> y <span class="op">&gt;=</span> Board.BoardHeight:</a>
<a class="sourceLine" id="cb1-332" data-line-number="332">                <span class="cf">return</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-333" data-line-number="333">                </a>
<a class="sourceLine" id="cb1-334" data-line-number="334">            <span class="cf">if</span> <span class="va">self</span>.shapeAt(x, y) <span class="op">!=</span> Tetrominoe.NoShape:</a>
<a class="sourceLine" id="cb1-335" data-line-number="335">                <span class="cf">return</span> <span class="va">False</span></a>
<a class="sourceLine" id="cb1-336" data-line-number="336"></a>
<a class="sourceLine" id="cb1-337" data-line-number="337">        <span class="va">self</span>.curPiece <span class="op">=</span> newPiece</a>
<a class="sourceLine" id="cb1-338" data-line-number="338">        <span class="va">self</span>.curX <span class="op">=</span> newX</a>
<a class="sourceLine" id="cb1-339" data-line-number="339">        <span class="va">self</span>.curY <span class="op">=</span> newY</a>
<a class="sourceLine" id="cb1-340" data-line-number="340">        <span class="va">self</span>.update()</a>
<a class="sourceLine" id="cb1-341" data-line-number="341">        </a>
<a class="sourceLine" id="cb1-342" data-line-number="342">        <span class="cf">return</span> <span class="va">True</span></a>
<a class="sourceLine" id="cb1-343" data-line-number="343">        </a>
<a class="sourceLine" id="cb1-344" data-line-number="344"></a>
<a class="sourceLine" id="cb1-345" data-line-number="345">    <span class="kw">def</span> drawSquare(<span class="va">self</span>, painter, x, y, shape):</a>
<a class="sourceLine" id="cb1-346" data-line-number="346">        <span class="co">&#39;&#39;&#39;draws a square of a shape&#39;&#39;&#39;</span>        </a>
<a class="sourceLine" id="cb1-347" data-line-number="347">        </a>
<a class="sourceLine" id="cb1-348" data-line-number="348">        colorTable <span class="op">=</span> [<span class="bn">0x000000</span>, <span class="bn">0xCC6666</span>, <span class="bn">0x66CC66</span>, <span class="bn">0x6666CC</span>,</a>
<a class="sourceLine" id="cb1-349" data-line-number="349">                      <span class="bn">0xCCCC66</span>, <span class="bn">0xCC66CC</span>, <span class="bn">0x66CCCC</span>, <span class="bn">0xDAAA00</span>]</a>
<a class="sourceLine" id="cb1-350" data-line-number="350"></a>
<a class="sourceLine" id="cb1-351" data-line-number="351">        color <span class="op">=</span> QColor(colorTable[shape])</a>
<a class="sourceLine" id="cb1-352" data-line-number="352">        painter.fillRect(x <span class="op">+</span> <span class="dv">1</span>, y <span class="op">+</span> <span class="dv">1</span>, <span class="va">self</span>.squareWidth() <span class="op">-</span> <span class="dv">2</span>, </a>
<a class="sourceLine" id="cb1-353" data-line-number="353">            <span class="va">self</span>.squareHeight() <span class="op">-</span> <span class="dv">2</span>, color)</a>
<a class="sourceLine" id="cb1-354" data-line-number="354"></a>
<a class="sourceLine" id="cb1-355" data-line-number="355">        painter.setPen(color.lighter())</a>
<a class="sourceLine" id="cb1-356" data-line-number="356">        painter.drawLine(x, y <span class="op">+</span> <span class="va">self</span>.squareHeight() <span class="op">-</span> <span class="dv">1</span>, x, y)</a>
<a class="sourceLine" id="cb1-357" data-line-number="357">        painter.drawLine(x, y, x <span class="op">+</span> <span class="va">self</span>.squareWidth() <span class="op">-</span> <span class="dv">1</span>, y)</a>
<a class="sourceLine" id="cb1-358" data-line-number="358"></a>
<a class="sourceLine" id="cb1-359" data-line-number="359">        painter.setPen(color.darker())</a>
<a class="sourceLine" id="cb1-360" data-line-number="360">        painter.drawLine(x <span class="op">+</span> <span class="dv">1</span>, y <span class="op">+</span> <span class="va">self</span>.squareHeight() <span class="op">-</span> <span class="dv">1</span>,</a>
<a class="sourceLine" id="cb1-361" data-line-number="361">            x <span class="op">+</span> <span class="va">self</span>.squareWidth() <span class="op">-</span> <span class="dv">1</span>, y <span class="op">+</span> <span class="va">self</span>.squareHeight() <span class="op">-</span> <span class="dv">1</span>)</a>
<a class="sourceLine" id="cb1-362" data-line-number="362">        painter.drawLine(x <span class="op">+</span> <span class="va">self</span>.squareWidth() <span class="op">-</span> <span class="dv">1</span>, </a>
<a class="sourceLine" id="cb1-363" data-line-number="363">            y <span class="op">+</span> <span class="va">self</span>.squareHeight() <span class="op">-</span> <span class="dv">1</span>, x <span class="op">+</span> <span class="va">self</span>.squareWidth() <span class="op">-</span> <span class="dv">1</span>, y <span class="op">+</span> <span class="dv">1</span>)</a>
<a class="sourceLine" id="cb1-364" data-line-number="364"></a>
<a class="sourceLine" id="cb1-365" data-line-number="365"></a>
<a class="sourceLine" id="cb1-366" data-line-number="366"><span class="kw">class</span> Tetrominoe(<span class="bu">object</span>):</a>
<a class="sourceLine" id="cb1-367" data-line-number="367">    </a>
<a class="sourceLine" id="cb1-368" data-line-number="368">    NoShape <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-369" data-line-number="369">    ZShape <span class="op">=</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb1-370" data-line-number="370">    SShape <span class="op">=</span> <span class="dv">2</span></a>
<a class="sourceLine" id="cb1-371" data-line-number="371">    LineShape <span class="op">=</span> <span class="dv">3</span></a>
<a class="sourceLine" id="cb1-372" data-line-number="372">    TShape <span class="op">=</span> <span class="dv">4</span></a>
<a class="sourceLine" id="cb1-373" data-line-number="373">    SquareShape <span class="op">=</span> <span class="dv">5</span></a>
<a class="sourceLine" id="cb1-374" data-line-number="374">    LShape <span class="op">=</span> <span class="dv">6</span></a>
<a class="sourceLine" id="cb1-375" data-line-number="375">    MirroredLShape <span class="op">=</span> <span class="dv">7</span></a>
<a class="sourceLine" id="cb1-376" data-line-number="376"></a>
<a class="sourceLine" id="cb1-377" data-line-number="377"></a>
<a class="sourceLine" id="cb1-378" data-line-number="378"><span class="kw">class</span> Shape(<span class="bu">object</span>):</a>
<a class="sourceLine" id="cb1-379" data-line-number="379">    </a>
<a class="sourceLine" id="cb1-380" data-line-number="380">    coordsTable <span class="op">=</span> (</a>
<a class="sourceLine" id="cb1-381" data-line-number="381">        ((<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">0</span>)),</a>
<a class="sourceLine" id="cb1-382" data-line-number="382">        ((<span class="dv">0</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="op">-</span><span class="dv">1</span>, <span class="dv">0</span>),    (<span class="op">-</span><span class="dv">1</span>, <span class="dv">1</span>)),</a>
<a class="sourceLine" id="cb1-383" data-line-number="383">        ((<span class="dv">0</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">1</span>, <span class="dv">0</span>),     (<span class="dv">1</span>, <span class="dv">1</span>)),</a>
<a class="sourceLine" id="cb1-384" data-line-number="384">        ((<span class="dv">0</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">1</span>),     (<span class="dv">0</span>, <span class="dv">2</span>)),</a>
<a class="sourceLine" id="cb1-385" data-line-number="385">        ((<span class="op">-</span><span class="dv">1</span>, <span class="dv">0</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">1</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">1</span>)),</a>
<a class="sourceLine" id="cb1-386" data-line-number="386">        ((<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">1</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">1</span>),     (<span class="dv">1</span>, <span class="dv">1</span>)),</a>
<a class="sourceLine" id="cb1-387" data-line-number="387">        ((<span class="op">-</span><span class="dv">1</span>, <span class="dv">-1</span>),   (<span class="dv">0</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">1</span>)),</a>
<a class="sourceLine" id="cb1-388" data-line-number="388">        ((<span class="dv">1</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">-1</span>),    (<span class="dv">0</span>, <span class="dv">0</span>),     (<span class="dv">0</span>, <span class="dv">1</span>))</a>
<a class="sourceLine" id="cb1-389" data-line-number="389">    )</a>
<a class="sourceLine" id="cb1-390" data-line-number="390"></a>
<a class="sourceLine" id="cb1-391" data-line-number="391">    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-392" data-line-number="392">        </a>
<a class="sourceLine" id="cb1-393" data-line-number="393">        <span class="va">self</span>.coords <span class="op">=</span> [[<span class="dv">0</span>,<span class="dv">0</span>] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>)]</a>
<a class="sourceLine" id="cb1-394" data-line-number="394">        <span class="va">self</span>.pieceShape <span class="op">=</span> Tetrominoe.NoShape</a>
<a class="sourceLine" id="cb1-395" data-line-number="395"></a>
<a class="sourceLine" id="cb1-396" data-line-number="396">        <span class="va">self</span>.setShape(Tetrominoe.NoShape)</a>
<a class="sourceLine" id="cb1-397" data-line-number="397">        </a>
<a class="sourceLine" id="cb1-398" data-line-number="398"></a>
<a class="sourceLine" id="cb1-399" data-line-number="399">    <span class="kw">def</span> shape(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-400" data-line-number="400">        <span class="co">&#39;&#39;&#39;returns shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-401" data-line-number="401">        </a>
<a class="sourceLine" id="cb1-402" data-line-number="402">        <span class="cf">return</span> <span class="va">self</span>.pieceShape</a>
<a class="sourceLine" id="cb1-403" data-line-number="403">        </a>
<a class="sourceLine" id="cb1-404" data-line-number="404"></a>
<a class="sourceLine" id="cb1-405" data-line-number="405">    <span class="kw">def</span> setShape(<span class="va">self</span>, shape):</a>
<a class="sourceLine" id="cb1-406" data-line-number="406">        <span class="co">&#39;&#39;&#39;sets a shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-407" data-line-number="407">        </a>
<a class="sourceLine" id="cb1-408" data-line-number="408">        table <span class="op">=</span> Shape.coordsTable[shape]</a>
<a class="sourceLine" id="cb1-409" data-line-number="409">        </a>
<a class="sourceLine" id="cb1-410" data-line-number="410">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-411" data-line-number="411">            <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">2</span>):</a>
<a class="sourceLine" id="cb1-412" data-line-number="412">                <span class="va">self</span>.coords[i][j] <span class="op">=</span> table[i][j]</a>
<a class="sourceLine" id="cb1-413" data-line-number="413"></a>
<a class="sourceLine" id="cb1-414" data-line-number="414">        <span class="va">self</span>.pieceShape <span class="op">=</span> shape</a>
<a class="sourceLine" id="cb1-415" data-line-number="415">        </a>
<a class="sourceLine" id="cb1-416" data-line-number="416"></a>
<a class="sourceLine" id="cb1-417" data-line-number="417">    <span class="kw">def</span> setRandomShape(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-418" data-line-number="418">        <span class="co">&#39;&#39;&#39;chooses a random shape&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-419" data-line-number="419">        </a>
<a class="sourceLine" id="cb1-420" data-line-number="420">        <span class="va">self</span>.setShape(random.randint(<span class="dv">1</span>, <span class="dv">7</span>))</a>
<a class="sourceLine" id="cb1-421" data-line-number="421"></a>
<a class="sourceLine" id="cb1-422" data-line-number="422">        </a>
<a class="sourceLine" id="cb1-423" data-line-number="423">    <span class="kw">def</span> x(<span class="va">self</span>, index):</a>
<a class="sourceLine" id="cb1-424" data-line-number="424">        <span class="co">&#39;&#39;&#39;returns x coordinate&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-425" data-line-number="425">        </a>
<a class="sourceLine" id="cb1-426" data-line-number="426">        <span class="cf">return</span> <span class="va">self</span>.coords[index][<span class="dv">0</span>]</a>
<a class="sourceLine" id="cb1-427" data-line-number="427"></a>
<a class="sourceLine" id="cb1-428" data-line-number="428">        </a>
<a class="sourceLine" id="cb1-429" data-line-number="429">    <span class="kw">def</span> y(<span class="va">self</span>, index):</a>
<a class="sourceLine" id="cb1-430" data-line-number="430">        <span class="co">&#39;&#39;&#39;returns y coordinate&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-431" data-line-number="431">        </a>
<a class="sourceLine" id="cb1-432" data-line-number="432">        <span class="cf">return</span> <span class="va">self</span>.coords[index][<span class="dv">1</span>]</a>
<a class="sourceLine" id="cb1-433" data-line-number="433"></a>
<a class="sourceLine" id="cb1-434" data-line-number="434">        </a>
<a class="sourceLine" id="cb1-435" data-line-number="435">    <span class="kw">def</span> setX(<span class="va">self</span>, index, x):</a>
<a class="sourceLine" id="cb1-436" data-line-number="436">        <span class="co">&#39;&#39;&#39;sets x coordinate&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-437" data-line-number="437">        </a>
<a class="sourceLine" id="cb1-438" data-line-number="438">        <span class="va">self</span>.coords[index][<span class="dv">0</span>] <span class="op">=</span> x</a>
<a class="sourceLine" id="cb1-439" data-line-number="439"></a>
<a class="sourceLine" id="cb1-440" data-line-number="440">        </a>
<a class="sourceLine" id="cb1-441" data-line-number="441">    <span class="kw">def</span> setY(<span class="va">self</span>, index, y):</a>
<a class="sourceLine" id="cb1-442" data-line-number="442">        <span class="co">&#39;&#39;&#39;sets y coordinate&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-443" data-line-number="443">        </a>
<a class="sourceLine" id="cb1-444" data-line-number="444">        <span class="va">self</span>.coords[index][<span class="dv">1</span>] <span class="op">=</span> y</a>
<a class="sourceLine" id="cb1-445" data-line-number="445"></a>
<a class="sourceLine" id="cb1-446" data-line-number="446">        </a>
<a class="sourceLine" id="cb1-447" data-line-number="447">    <span class="kw">def</span> minX(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-448" data-line-number="448">        <span class="co">&#39;&#39;&#39;returns min x value&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-449" data-line-number="449">        </a>
<a class="sourceLine" id="cb1-450" data-line-number="450">        m <span class="op">=</span> <span class="va">self</span>.coords[<span class="dv">0</span>][<span class="dv">0</span>]</a>
<a class="sourceLine" id="cb1-451" data-line-number="451">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-452" data-line-number="452">            m <span class="op">=</span> <span class="bu">min</span>(m, <span class="va">self</span>.coords[i][<span class="dv">0</span>])</a>
<a class="sourceLine" id="cb1-453" data-line-number="453"></a>
<a class="sourceLine" id="cb1-454" data-line-number="454">        <span class="cf">return</span> m</a>
<a class="sourceLine" id="cb1-455" data-line-number="455"></a>
<a class="sourceLine" id="cb1-456" data-line-number="456">        </a>
<a class="sourceLine" id="cb1-457" data-line-number="457">    <span class="kw">def</span> maxX(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-458" data-line-number="458">        <span class="co">&#39;&#39;&#39;returns max x value&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-459" data-line-number="459">        </a>
<a class="sourceLine" id="cb1-460" data-line-number="460">        m <span class="op">=</span> <span class="va">self</span>.coords[<span class="dv">0</span>][<span class="dv">0</span>]</a>
<a class="sourceLine" id="cb1-461" data-line-number="461">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-462" data-line-number="462">            m <span class="op">=</span> <span class="bu">max</span>(m, <span class="va">self</span>.coords[i][<span class="dv">0</span>])</a>
<a class="sourceLine" id="cb1-463" data-line-number="463"></a>
<a class="sourceLine" id="cb1-464" data-line-number="464">        <span class="cf">return</span> m</a>
<a class="sourceLine" id="cb1-465" data-line-number="465"></a>
<a class="sourceLine" id="cb1-466" data-line-number="466">        </a>
<a class="sourceLine" id="cb1-467" data-line-number="467">    <span class="kw">def</span> minY(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-468" data-line-number="468">        <span class="co">&#39;&#39;&#39;returns min y value&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-469" data-line-number="469">        </a>
<a class="sourceLine" id="cb1-470" data-line-number="470">        m <span class="op">=</span> <span class="va">self</span>.coords[<span class="dv">0</span>][<span class="dv">1</span>]</a>
<a class="sourceLine" id="cb1-471" data-line-number="471">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-472" data-line-number="472">            m <span class="op">=</span> <span class="bu">min</span>(m, <span class="va">self</span>.coords[i][<span class="dv">1</span>])</a>
<a class="sourceLine" id="cb1-473" data-line-number="473"></a>
<a class="sourceLine" id="cb1-474" data-line-number="474">        <span class="cf">return</span> m</a>
<a class="sourceLine" id="cb1-475" data-line-number="475"></a>
<a class="sourceLine" id="cb1-476" data-line-number="476">        </a>
<a class="sourceLine" id="cb1-477" data-line-number="477">    <span class="kw">def</span> maxY(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-478" data-line-number="478">        <span class="co">&#39;&#39;&#39;returns max y value&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-479" data-line-number="479">        </a>
<a class="sourceLine" id="cb1-480" data-line-number="480">        m <span class="op">=</span> <span class="va">self</span>.coords[<span class="dv">0</span>][<span class="dv">1</span>]</a>
<a class="sourceLine" id="cb1-481" data-line-number="481">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-482" data-line-number="482">            m <span class="op">=</span> <span class="bu">max</span>(m, <span class="va">self</span>.coords[i][<span class="dv">1</span>])</a>
<a class="sourceLine" id="cb1-483" data-line-number="483"></a>
<a class="sourceLine" id="cb1-484" data-line-number="484">        <span class="cf">return</span> m</a>
<a class="sourceLine" id="cb1-485" data-line-number="485"></a>
<a class="sourceLine" id="cb1-486" data-line-number="486">        </a>
<a class="sourceLine" id="cb1-487" data-line-number="487">    <span class="kw">def</span> rotateLeft(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-488" data-line-number="488">        <span class="co">&#39;&#39;&#39;rotates shape to the left&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-489" data-line-number="489">        </a>
<a class="sourceLine" id="cb1-490" data-line-number="490">        <span class="cf">if</span> <span class="va">self</span>.pieceShape <span class="op">==</span> Tetrominoe.SquareShape:</a>
<a class="sourceLine" id="cb1-491" data-line-number="491">            <span class="cf">return</span> <span class="va">self</span></a>
<a class="sourceLine" id="cb1-492" data-line-number="492"></a>
<a class="sourceLine" id="cb1-493" data-line-number="493">        result <span class="op">=</span> Shape()</a>
<a class="sourceLine" id="cb1-494" data-line-number="494">        result.pieceShape <span class="op">=</span> <span class="va">self</span>.pieceShape</a>
<a class="sourceLine" id="cb1-495" data-line-number="495">        </a>
<a class="sourceLine" id="cb1-496" data-line-number="496">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-497" data-line-number="497">            </a>
<a class="sourceLine" id="cb1-498" data-line-number="498">            result.setX(i, <span class="va">self</span>.y(i))</a>
<a class="sourceLine" id="cb1-499" data-line-number="499">            result.setY(i, <span class="op">-</span><span class="va">self</span>.x(i))</a>
<a class="sourceLine" id="cb1-500" data-line-number="500"></a>
<a class="sourceLine" id="cb1-501" data-line-number="501">        <span class="cf">return</span> result</a>
<a class="sourceLine" id="cb1-502" data-line-number="502"></a>
<a class="sourceLine" id="cb1-503" data-line-number="503">        </a>
<a class="sourceLine" id="cb1-504" data-line-number="504">    <span class="kw">def</span> rotateRight(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-505" data-line-number="505">        <span class="co">&#39;&#39;&#39;rotates shape to the right&#39;&#39;&#39;</span></a>
<a class="sourceLine" id="cb1-506" data-line-number="506">        </a>
<a class="sourceLine" id="cb1-507" data-line-number="507">        <span class="cf">if</span> <span class="va">self</span>.pieceShape <span class="op">==</span> Tetrominoe.SquareShape:</a>
<a class="sourceLine" id="cb1-508" data-line-number="508">            <span class="cf">return</span> <span class="va">self</span></a>
<a class="sourceLine" id="cb1-509" data-line-number="509"></a>
<a class="sourceLine" id="cb1-510" data-line-number="510">        result <span class="op">=</span> Shape()</a>
<a class="sourceLine" id="cb1-511" data-line-number="511">        result.pieceShape <span class="op">=</span> <span class="va">self</span>.pieceShape</a>
<a class="sourceLine" id="cb1-512" data-line-number="512">        </a>
<a class="sourceLine" id="cb1-513" data-line-number="513">        <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</a>
<a class="sourceLine" id="cb1-514" data-line-number="514">            </a>
<a class="sourceLine" id="cb1-515" data-line-number="515">            result.setX(i, <span class="op">-</span><span class="va">self</span>.y(i))</a>
<a class="sourceLine" id="cb1-516" data-line-number="516">            result.setY(i, <span class="va">self</span>.x(i))</a>
<a class="sourceLine" id="cb1-517" data-line-number="517"></a>
<a class="sourceLine" id="cb1-518" data-line-number="518">        <span class="cf">return</span> result</a>
<a class="sourceLine" id="cb1-519" data-line-number="519"></a>
<a class="sourceLine" id="cb1-520" data-line-number="520"></a>
<a class="sourceLine" id="cb1-521" data-line-number="521"><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&#39;__main__&#39;</span>:</a>
<a class="sourceLine" id="cb1-522" data-line-number="522">    </a>
<a class="sourceLine" id="cb1-523" data-line-number="523">    app <span class="op">=</span> QApplication([])</a>
<a class="sourceLine" id="cb1-524" data-line-number="524">    tetris <span class="op">=</span> Tetris()    </a>
<a class="sourceLine" id="cb1-525" data-line-number="525">    sys.exit(app.exec_())</a></code></pre></div>
<p>游戏很简单，所以也就很好理解。程序加载之后游戏也就直接开始了，可以用P键暂停游戏，空格键让方块直接落到最下面。游戏的速度是固定的，并没有实现加速的功能。分数就是游戏中消除的行数。</p>
<pre><code class="language-python">self.tboard = Board(self)
self.setCentralWidget(self.tboard)</code></pre>
<p>创建了一个Board类的实例，并设置为应用的中心组件。</p>
<pre><code class="language-python">self.statusbar = self.statusBar()        
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)</code></pre>
<p>创建一个<code>statusbar</code>来显示三种信息：消除的行数，游戏暂停状态或者游戏结束状态。<code>msg2Statusbar</code>是一个自定义的信号，用在（和）Board类（交互），<code>showMessage()</code>方法是一个内建的，用来在statusbar上显示信息的方法。</p>
<pre><code class="language-python">self.tboard.start()</code></pre>
<p>初始化游戏：</p>
<pre><code class="language-python">class Board(QFrame):
    
    msg2Statusbar = pyqtSignal(str)
...    </code></pre>
<p>创建了一个自定义信号<code>msg2Statusbar</code>，当我们想往<code>statusbar</code>里显示信息的时候，发出这个信号就行了。</p>
<pre><code class="language-python">BoardWidth = 10
BoardHeight = 22
Speed = 300</code></pre>
<p>这些是<code>Board</code>类的变量。<code>BoardWidth</code>和<code>BoardHeight</code>分别是board的宽度和高度。<code>Speed</code>是游戏的速度，每300ms出现一个新的方块。</p>
<pre><code class="language-python">...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...</code></pre>
<p>在<code>initBoard()</code>里初始化了一些重要的变量。<code>self.board</code>定义了方块的形状和位置，取值范围是0-7。</p>
<pre><code class="language-python">def shapeAt(self, x, y):
    return self.board[(y * Board.BoardWidth) + x]</code></pre>
<p><code>shapeAt()</code>决定了board里方块的的种类。</p>
<pre><code class="language-python">def squareWidth(self):
    return self.contentsRect().width() // Board.BoardWidth</code></pre>
<p>board的大小可以动态的改变。所以方格的大小也应该随之变化。<code>squareWidth()</code>计算并返回每个块应该占用多少像素–也即<code>Board.BoardWidth</code>。</p>
<pre><code class="language-python">def pause(self):
    &#39;&#39;&#39;pauses game&#39;&#39;&#39;

    if not self.isStarted:
        return

    self.isPaused = not self.isPaused

    if self.isPaused:
        self.timer.stop()
        self.msg2Statusbar.emit(&quot;paused&quot;)

    else:
        self.timer.start(Board.Speed, self)
        self.msg2Statusbar.emit(str(self.numLinesRemoved))

    self.update()</code></pre>
<p><code>pause()</code>方法用来暂停游戏，停止计时并在<code>statusbar</code>上显示一条信息。</p>
<pre><code class="language-python">def paintEvent(self, event):
    &#39;&#39;&#39;paints all shapes of the game&#39;&#39;&#39;

    painter = QPainter(self)
    rect = self.contentsRect()
...</code></pre>
<p>渲染是在paintEvent()方法里发生的<code>QPainter</code>负责PyQt5里所有低级绘画操作。</p>
<pre><code class="language-python">for i in range(Board.BoardHeight):
    for j in range(Board.BoardWidth):
        shape = self.shapeAt(j, Board.BoardHeight - i - 1)
        
        if shape != Tetrominoe.NoShape:
            self.drawSquare(painter,
                rect.left() + j * self.squareWidth(),
                boardTop + i * self.squareHeight(), shape)</code></pre>
<p>渲染游戏分为两步。第一步是先画出所有已经落在最下面的的图，这些保存在<code>self.board</code>里。可以使用<code>shapeAt()</code>查看这个这个变量。</p>
<pre><code class="language-python">if self.curPiece.shape() != Tetrominoe.NoShape:
    
    for i in range(4):
        
        x = self.curX + self.curPiece.x(i)
        y = self.curY - self.curPiece.y(i)
        self.drawSquare(painter, rect.left() + x * self.squareWidth(),
            boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
            self.curPiece.shape())</code></pre>
<p>第二步是画出更在下落的方块。</p>
<pre><code class="language-python">elif key == Qt.Key_Right:
    self.tryMove(self.curPiece, self.curX + 1, self.curY)</code></pre>
<p>在<code>keyPressEvent()</code>方法获得用户按下的按键。如果按下的是右方向键，就尝试把方块向右移动，说尝试是因为有可能到边界不能移动了。</p>
<pre><code class="language-python">elif key == Qt.Key_Up:
    self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)</code></pre>
<p>上方向键是把方块向左旋转一下</p>
<pre><code class="language-python">elif key == Qt.Key_Space:
    self.dropDown()</code></pre>
<p>空格键会直接把方块放到底部</p>
<pre><code class="language-python">elif key == Qt.Key_D:
    self.oneLineDown()</code></pre>
<p>D键是加速一次下落速度。</p>
<pre><code class="language-python">def tryMove(self, newPiece, newX, newY):
    
    for i in range(4):
        
        x = newX + newPiece.x(i)
        y = newY - newPiece.y(i)
        
        if x &lt; 0 or x &gt;= Board.BoardWidth or y &lt; 0 or y &gt;= Board.BoardHeight:
            return False
            
        if self.shapeAt(x, y) != Tetrominoe.NoShape:
            return False

    self.curPiece = newPiece
    self.curX = newX
    self.curY = newY
    self.update()
    return True</code></pre>
<p><code>tryMove()</code>是尝试移动方块的方法。如果方块已经到达board的边缘或者遇到了其他方块，就返回False。否则就把方块下落到想要</p>
<pre><code class="language-python">def timerEvent(self, event):
    
    if event.timerId() == self.timer.timerId():
        
        if self.isWaitingAfterLine:
            self.isWaitingAfterLine = False
            self.newPiece()
        else:
            self.oneLineDown()
            
    else:
        super(Board, self).timerEvent(event)</code></pre>
<p>在计时器事件里，要么是等一个方块下落完之后创建一个新的方块，要么是让一个方块直接落到底（move a falling piece one line down）。</p>
<pre><code class="language-python">def clearBoard(self):
    
    for i in range(Board.BoardHeight * Board.BoardWidth):
        self.board.append(Tetrominoe.NoShape)</code></pre>
<p><code>clearBoard(</code>)方法通过<code>Tetrominoe.NoShape</code>清空<code>broad</code>。</p>
<pre><code class="language-python">def removeFullLines(self):
    
    numFullLines = 0
    rowsToRemove = []

    for i in range(Board.BoardHeight):
        
        n = 0
        for j in range(Board.BoardWidth):
            if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                n = n + 1

        if n == 10:
            rowsToRemove.append(i)

    rowsToRemove.reverse()
    

    for m in rowsToRemove:
        
        for k in range(m, Board.BoardHeight):
            for l in range(Board.BoardWidth):
                    self.setShapeAt(l, k, self.shapeAt(l, k + 1))

    numFullLines = numFullLines + len(rowsToRemove)
 ...</code></pre>
<p>如果方块碰到了底部，就调用<code>removeFullLines()</code>方法，找到所有能消除的行消除它们。消除的具体动作就是把符合条件的行消除掉之后，再把它上面的行下降一行。注意移除满行的动作是倒着来的，因为我们是按照重力来表现游戏的，如果不这样就有可能出现有些方块浮在空中的现象。</p>
<pre><code class="language-python">def newPiece(self):
    
    self.curPiece = Shape()
    self.curPiece.setRandomShape()
    self.curX = Board.BoardWidth // 2 + 1
    self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
    
    if not self.tryMove(self.curPiece, self.curX, self.curY):
        
        self.curPiece.setShape(Tetrominoe.NoShape)
        self.timer.stop()
        self.isStarted = False
        self.msg2Statusbar.emit(&quot;Game over&quot;)</code></pre>
<p><code>newPiece()</code>方法是用来创建形状随机的方块。如果随机的方块不能正确的出现在预设的位置，游戏结束。</p>
<pre><code class="language-python">class Tetrominoe(object):
    
    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7</code></pre>
<p><code>Tetrominoe</code>类保存了所有方块的形状。我们还定义了一个<code>NoShape</code>的空形状。</p>
<p>Shape类保存类方块内部的信息。</p>
<pre><code class="language-python">class Shape(object):
    
    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ...
    )
...    </code></pre>
<p>coordsTable元组保存了所有的方块形状的组成。是一个构成方块的坐标模版。</p>
<pre><code class="language-python">self.coords = [[0,0] for i in range(4)]</code></pre>
<p>上面创建了一个新的空坐标数组，这个数组将用来保存方块的坐标。</p>
<p>坐标系示意图：</p>
<figure>
<img class="whitelist" src="docs/PyQt5/images/11-coordinates.png" alt="coordinates" />
</figure>
<p>上面的图片可以帮助我们更好的理解坐标值的意义。比如元组<code>(0, -1), (0, 0), (-1, 0), (-1, -1)</code>代表了一个Z形状的方块。这个图表就描绘了这个形状。</p>
<pre><code class="language-python">def rotateLeft(self):
    
    if self.pieceShape == Tetrominoe.SquareShape:
        return self

    result = Shape()
    result.pieceShape = self.pieceShape
    
    for i in range(4):
        
        result.setX(i, self.y(i))
        result.setY(i, -self.x(i))

    return result</code></pre>
<p><code>rotateLeft()</code>方法向右旋转一个方块。正方形的方块就没必要旋转，就直接返回了。其他的是返回一个新的，能表示这个形状旋转了的坐标。</p>
<p>程序展示：</p>
<figure>
<img class="whitelist" src="docs/PyQt5/images/11-tetris.png" alt="Tetris" />
</figure>
